// =======================================================
// Microorganismo Mpkstroff
// 
// Inspirado en Serguei Dimitri Mpkstroff (compositor eslavo) [Les Luthiers]
//
// Videos ilustrativos
// http://www.youtube.com/watch?v=u6FUIK3z6xc
// http://www.youtube.com/watch?v=rvGXbzvlpAU
//
// =======================================================

package MOs;

import controller.java.Microorganism;
import controller.java.Movement;
import controller.java.Petri;
import data.java.Defs;
import java.awt.Point;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

/**
 * @Autores Sebastián Vanrell y Leandro Bugnon
 * @Fecha 9 de Diciembre de 2010
 * @Institución Facultad de Ingeniería, Universidad Nacional de Entre Ríos
 */

public class Mpkstroff extends Microorganism
{
	public String getName()
	{
		return "Mpkstroff";
	}

	public String getAuthor()
	{
		return "Lea Seba";
	}

	public String getAffiliation()
	{
		return "FI-UNER";
	}

	Petri petri = Petri.getInstance();
	static Panorama panorama;

	// parametros de mo
	int radioDensidad = Cte.RADIODENSIDAD;
	int radioAtaque = Cte.RADIOATAQUE;
	int radioComida = Cte.RADIOCOMIDA;
	int radioEscape = Cte.RADIOESCAPE;
	int turno;
	float fEscapar = Cte.fESCAPAR;
	float fAtacar = Cte.fATACAR;
	boolean fertil;
	boolean viajero;

	/** Movimiento relativo anterior */
	Movement exMov = new Movement(0, 0);

	public Mpkstroff()
	{
		if (panorama == null)
			panorama = new Panorama();
		turno = panorama.turnoColonia;
		fertil = Math.random() < 0.4;
		viajero = Math.random() < 0.3;
		// viajero=false;
		panorama.nos.habitantes++;
	}

	/**
	 * Define el movimiento del MO. Una vez por turno se actualiza el panorama y
	 * cada individuo evalúa, en orden, si hay un evento de combate que atender,
	 * sino busca alimento.
	 */
	public void move(Movement mov)
	{
		mov.dx = mov.dy = 0;

		// genera id si no estan aun
		verId();

		// Detecta el inicio de batalla y realiza lo mismo que el constructor
		if (panorama.nos.habitantes == 50 && panorama.ellos.habitantes == 50
				&& Math.abs(panorama.nos.energiaTotal - 50000f) / 50000f < 0.05
				&& Math.abs(panorama.ellos.energiaTotal - 50000f) / 50000f < 0.05)
		{
			panorama = new Panorama();
			turno = 0;
		}
		// Hace que todos los bichos reinicialicen su turno si se creo un nuevo Panorama
		if (panorama.turnoColonia == 1 && turno > 1)
			turno = 0;
		if (panorama.turnoColonia - 1 > turno)
			turno = panorama.turnoColonia - 1;

		// genera id si no estan aun
		verId();

		turno++;
		if (turno > panorama.turnoColonia)
		{
			panorama.turnoColonia++;
			panorama.actualizar();
		}

		definirComportamiento(mov);
		if (panorama.codigoColonia == Cte.FINALIZAR)
		{
			if (!combate(mov))
				alimentarse(mov);
		} else
			alimentarse(mov);

		// if(petri.getDistNutri()==Cte.LATTICE && Math.random()>0.7)
		// {mov.dx=0;mov.dy=0;}

		exMov = new Movement(mov.dx, mov.dy);
	}

	private void verId()
	{
		panorama.ellos.id = -1;
		panorama.nos.id = petri.getOpponent(pos.x, pos.y);
		for (int y = 0; y < Defs.DIAMETER; y++)
			for (int x = 0; x < Defs.DIAMETER; x++)
			{
				if (panorama.ellos.id == -1 && petri.getOpponent(x, y) != -1
						&& petri.getOpponent(x, y) != panorama.nos.id)
				{
					panorama.ellos.id = petri.getOpponent(x, y);
					return;
				}
			}
	}

	/**
	 * Devuelve true si el MO se dividirá en el turno actual. Se evalua antes
	 * que mover
	 */
	public boolean mitosis() // TODO MODIFICADO. AUMENTA PARA GAUS Y ANILLO, AUMENTA fInfertiles
	{
		boolean moneda = Math.random() < 0.5;

		// float turnosRestantes;
		// turnosRestantes = panorama.ellos.energiaTotal * (1 + panorama.ellos.tendencia / 100);
		// System.out.println(turnosRestantes);
		// TODO ACA DEFINIR, SI YA SE TERMINA, QUE HAGAN MITOSIS RAPIDA

		boolean cumpleNorma = moneda && fertil && panorama.mapa.comidaMax > 3000
				&& panorama.mapa.get(pos.x, pos.y).densidadEllos.poblacion == 0
				&& panorama.codigoColonia != Cte.FINALIZAR && panorama.ellos.habitantes < 50;

		switch (petri.getDistNutri())
		{
		case Cte.RINGS:
			return cumpleNorma && panorama.mapa.get(pos.x, pos.y).nivelDeComida > 1
					&& panorama.nos.energiaProm > panorama.ellos.energiaProm && panorama.nos.habitantes < 80;

		case Cte.GAUSS:
			return cumpleNorma
					&& panorama.mapa.get(pos.x, pos.y).nivelDeComida > 0.6f * panorama.mapa.numNiveles
					&& panorama.nos.energiaProm > panorama.ellos.energiaProm && panorama.nos.habitantes < 60;

		case Cte.LATTICE:
			return cumpleNorma && panorama.mapa.get(pos.x, pos.y).nivelDeComida > 3
					&& panorama.nos.energiaProm > 2 * panorama.ellos.energiaProm
					&& panorama.nos.habitantes < 50;

		case Cte.BAR:
			return false;
		case Cte.SORPRESA:
		default:
			return cumpleNorma
					&& panorama.mapa.get(pos.x, pos.y).nivelDeComida > 0.6f * panorama.mapa.numNiveles
					&& panorama.nos.energiaProm > panorama.ellos.energiaProm && panorama.nos.habitantes < 60;
		}
	}

	/**
	 * Determina el comportamiento individual segun el panorama y elementos de
	 * su vecindad
	 */
	private void definirComportamiento(Movement mov)
	{
		switch (petri.getDistNutri())
		{
		case Cte.BAR:
			evaluarBar();
			break;
		case Cte.GAUSS:
			evaluarGauss();
			break;
		case Cte.LATTICE:
			evaluarLattice(mov);
			if (panorama.mapa.get(pos.x, pos.y).nivelDeComida > 2.0f * panorama.mapa.numNiveles / 3.0f)
			{
				radioComida = 2;
			}
			break;
		case Cte.RINGS:
			evaluarRings();
			break;
		case Cte.SORPRESA:
			evaluarSorpresa();
			break;
		default:
			evaluarDefault();
		}
		if (panorama.ellos.habitantes > 100)
			fAtacar = 0;
	}

	private void evaluarSorpresa() // TODO HACER AUN
	{
		radioDensidad = 6;
		switch (panorama.codigoColonia)
		{
		case Cte.NORMAL:

			radioAtaque = 3;
			radioComida = 30;
			fAtacar = 2f;
			fEscapar = 0.7f;
			radioEscape = 1;

			// si andan en patota
			if (panorama.mapa.get(pos.x, pos.y).densidadNos.energia > 0.8 * panorama.mapa.densidadNosMax)
				fAtacar = 1.3f;

			// si esta en el pico
			if (panorama.mapa.get(pos.x, pos.y).nivelDeComida >= panorama.mapa.numNiveles / 2)
			{
				fAtacar = 1.45f;
				radioComida = 2;
			}

			if (panorama.mapa.get(pos.x, pos.y).nivelDeComida == panorama.mapa.numNiveles)
				radioAtaque = 0;

			break;

		case Cte.AGUANTAR:
			radioAtaque = 0;
			radioEscape = 2;
			fEscapar = 10f;

			radioComida = 20;
			// si esta en el pico
			if (panorama.mapa.get(pos.x, pos.y).nivelDeComida >= panorama.mapa.numNiveles / 2)
				radioComida = 2;

			break;

		case Cte.FINALIZAR:
			radioAtaque = Math.max(getCantidadMov(new Point(0, 0), new Point(pos.x, pos.y)), getCantidadMov(
					new Point(Defs.DIAMETER, Defs.DIAMETER), new Point(pos.x, pos.y)));
			radioComida = 20;

			fAtacar = 0.6f;
			fEscapar = 0.3f;
			// si esta en el pico
			if (panorama.mapa.get(pos.x, pos.y).nivelDeComida >= panorama.mapa.numNiveles / 2)
				radioComida = 2;

			break;
		}
		if (panorama.ellos.habitantes > 100)
			fAtacar = 0;
	}

	private void evaluarRings()
	{
		switch (panorama.codigoColonia)
		{
		case Cte.NORMAL:
			// fuera del anillo
			radioAtaque = 3;
			radioComida = 40;
			fAtacar = 5f;
			fEscapar = 0.7f;
			// dentro
			if (panorama.mapa.get(pos.x, pos.y).nivelDeComida > 0)
			{
				radioAtaque = 3;
				radioComida = 4;
				fAtacar = 1.4f;
				fEscapar = 0.7f;
				radioEscape = 1;

				if (panorama.mapa.get(pos.x, pos.y).densidadNos.energia > 0.7 * panorama.mapa.densidadNosMax)
					fAtacar = 1.3f;
			}
			break;

		case Cte.AGUANTAR:
			radioAtaque = 0;
			radioEscape = 2;
			fEscapar = 10f;

			radioComida = 40;
			if (panorama.mapa.get(pos.x, pos.y).nivelDeComida > panorama.mapa.numNiveles / 2)
				radioComida = 4;

			break;
		case Cte.FINALIZAR:
			radioAtaque = Math.max(getCantidadMov(new Point(0, 0), new Point(pos.x, pos.y)), getCantidadMov(
					new Point(Defs.DIAMETER, Defs.DIAMETER), new Point(pos.x, pos.y)));
			radioComida = 40;
			if (panorama.mapa.get(pos.x, pos.y).nivelDeComida > panorama.mapa.numNiveles / 2)
				radioComida = 4;
			fAtacar = 0.6f;
			fEscapar = 0.3f;
			break;
		}
	}

	private void evaluarLattice(Movement mov)
	{
		switch (panorama.codigoColonia)
		{
		case Cte.NORMAL:
			// Intento de definir zona mas rica, se calcula en analizar //TODO
			/*
			 * if(Math.abs(pos.x-panorama.centroMax.x)<=panorama.radioMax &&
			 * Math.abs(pos.y-panorama.centroMax.y)<=panorama.radioMax)
			 * {
			 * //DENTRO DE LA ZONA DE MAS NUTRIENTE
			 * radioAtaque = 4;
			 * radioComida = 3;
			 * fAtacar = 1.5f;
			 * fEscapar = 0.85f;
			 * radioEscape = 1;
			 * System.out.println(pos.x+","+pos.y);
			 * System.out.println("en radio"+" centro:"+panorama.centroMax.x+","+panorama.centroMax.y);
			 * System.out.println();
			 * }
			 * else
			 * {
			 * //FUERA DE LA ZONA. BUSCA EL centro
			 * mov.dx=0;mov.dy=0;
			 * movimientoPosible(panorama.centroMax, new Point(pos.x,pos.y), mov, exMov);
			 * System.out.println(pos.x+","+pos.y);
			 * System.out.println("fuerade radio"+" centro:"+panorama.centroMax.x+","+panorama.centroMax.y);
			 * System.out.println();
			 * }
			 */
			radioAtaque = 2;
			radioComida = 8;
			fAtacar = 1.5f;
			radioEscape = 1;
			fEscapar = 0.8f;

			// cima
			if (panorama.mapa.get(pos.x, pos.y).nivelDeComida > panorama.mapa.numNiveles / 2)
			{
				radioAtaque = 2;
				radioComida = 3;
				fAtacar = 1.3f;
				fEscapar = 0.85f;
				radioEscape = 1;
			}
			if (viajero)
				radioComida = 30; // TODO
			if (panorama.mapa.get(pos.x, pos.y).densidadNos.energia > 0.7 * panorama.mapa.densidadNosMax)
				fAtacar = 1.2f;
			break;

		case Cte.AGUANTAR:
			radioAtaque = 0;
			radioEscape = 2;
			fEscapar = 10f;
			break;
		case Cte.FINALIZAR:
			radioAtaque = Math.max(getCantidadMov(new Point(0, 0), new Point(pos.x, pos.y)), getCantidadMov(
					new Point(Defs.DIAMETER, Defs.DIAMETER), new Point(pos.x, pos.y)));
			fAtacar = 0.6f;
			fEscapar = 0.3f;
			break;
		case Cte.SUICIDARSE:

			radioAtaque = Math.max(getCantidadMov(new Point(0, 0), new Point(pos.x, pos.y)), getCantidadMov(
					new Point(Defs.DIAMETER, Defs.DIAMETER), new Point(pos.x, pos.y)));
			fAtacar = 0;
			fEscapar = 0;

			break;
		}
		if (panorama.ellos.habitantes > 100)
			fAtacar = 0;
	}

	private void evaluarGauss()
	{
		radioDensidad = 8;
		switch (panorama.codigoColonia)
		{
		case Cte.NORMAL:

			radioAtaque = 3;
			radioComida = 30;
			fAtacar = 2f;
			fEscapar = 0.7f;
			radioEscape = 1;

			// si andan en patota
			if (panorama.mapa.get(pos.x, pos.y).densidadNos.energia > 0.8 * panorama.mapa.densidadNosMax)
				fAtacar = 1.3f;

			// si esta en el pico
			if (panorama.mapa.get(pos.x, pos.y).nivelDeComida >= 2 * panorama.mapa.numNiveles / 3)
			{
				fAtacar = 1.45f;
				radioComida = 2;
			}

			if (panorama.mapa.get(pos.x, pos.y).nivelDeComida == panorama.mapa.numNiveles)
				radioAtaque = 0;

			break;

		case Cte.AGUANTAR:
			radioAtaque = 0;
			radioEscape = 2;
			fEscapar = 10f;

			radioComida = 20;
			// si esta en el pico
			if (panorama.mapa.get(pos.x, pos.y).nivelDeComida >= 2 * panorama.mapa.numNiveles / 3)
				radioComida = 2;

			break;

		case Cte.FINALIZAR:
			radioAtaque = Math.max(getCantidadMov(new Point(0, 0), new Point(pos.x, pos.y)), getCantidadMov(
					new Point(Defs.DIAMETER, Defs.DIAMETER), new Point(pos.x, pos.y)));
			radioComida = 20;

			fAtacar = 0.6f;
			fEscapar = 0.3f;
			// si esta en el pico
			if (panorama.mapa.get(pos.x, pos.y).nivelDeComida >= 2 * panorama.mapa.numNiveles / 3)
				radioComida = 2;

			break;
		}
		if (panorama.ellos.habitantes > 100)
			fAtacar = 0;
	}

	private void evaluarDefault()
	{
		evaluarSorpresa();
		return;
	}

	private void evaluarBar()
	{
		switch (panorama.codigoColonia)
		{
		case Cte.NORMAL:
			// fuera de la barra
			radioAtaque = 3;
			radioComida = 25;
			fAtacar = 5f;
			fEscapar = 0.6f;
			// dentro
			if (panorama.mapa.get(pos.x, pos.y).nivelDeComida > 0)
			{
				radioAtaque = 3;
				radioComida = 4;
				fAtacar = 1.5f;
				fEscapar = 0.4f;
				radioEscape = 2;
			}
			break;
		case Cte.AGUANTAR:
			radioAtaque = 0;
			radioEscape = 2;
			fEscapar = 10f;
			radioComida = 25;
			if (panorama.mapa.get(pos.x, pos.y).nivelDeComida > panorama.mapa.numNiveles / 2)
				radioComida = 4;
			break;
		case Cte.FINALIZAR:
			radioAtaque = Math.max(getCantidadMov(new Point(0, 0), new Point(pos.x, pos.y)), getCantidadMov(
					new Point(Defs.DIAMETER, Defs.DIAMETER), new Point(pos.x, pos.y)));
			radioComida = 25;
			fAtacar = 0.6f;
			fEscapar = 0.3f;
			break;
		}
		if (panorama.ellos.habitantes > 100)
			fAtacar = 0;
	}

	/**
	 * Evalua la posibilidad de combatir (o huir) un MO vecino. La pertenencia
	 * como vecino está determinada por el radioAtaque (y posiblemente
	 * radioEscape, aunque se define r=2 por defecto). Si miE es la energia
	 * perdida en combate de mi MO y suE es la perdida del contrincante,
	 * entonces atacará si suE/miE>fAtaque y escapa si suE/miE<fEscapar. Tener
	 * en cuenta que siempre fAtacar>fEscapar
	 */
	private boolean combate(Movement mov)
	{
		if (!escapar(mov))
			atacar(mov);

		return mov.isMoved();
	}

	boolean atacar(Movement mov)
	{
		float dif = 0; // diferencia de energia absoluta entre los dos
		float miEP = 0; // ENERGIAS PERDIDAS
		float suEP = 0;
		Point p;
		// Point pfinal = new Point();
		int radio = 1;

		float eminima = 500000;

		// EN CASO DE QUE SEA FINALIZAR, DETERMINA SI ES UN SUJETO VÁLIDO PARA ATACAR. SINO, DEVUELVE FALSE

		if (panorama.codigoColonia == Cte.FINALIZAR)
		{
			// METODO HORRENDO: RECORRE EL MAP HASTA UN VALOR DADO DE contadorAtacantes. Si no esta en uno de
			// los
			// que recorrio, no se mueve
			int contador = (int) (0.33 * panorama.contadorAtacantes); // solo atacan los 50% mas cercanos a
			// enemigos
			Vector<Point> vp = new Vector<Point>();
			int r = 1;
			boolean encontrado = false;

			while (contador > 0 && !encontrado)
			{
				vp = panorama.atacantes.get(r);
				if (vp != null)
					for (int e = 0; e < vp.size(); e++)
					{
						if (pos.x == vp.get(e).x && pos.y == vp.get(e).y)
							encontrado = true;
						contador--;
					}
				r++;
				// if(vp!=null)
				// vp.clear();
			}
			// si no esta en la lista, no ataca
			if (!encontrado)
				return false;
		}

		while (radio <= radioAtaque)
		{
			for (int x = pos.x - radio; x <= pos.x + radio; x++)
				for (int y = pos.y - radio; y <= pos.y + radio; y++)
				{
					p = new Point(x, y);
					// NO ESTA USANDO EL MAPA DE POSIBLES PORQ ES MEJOR, YA QUE PUEDEN ATACAR ENTRE
					// VARIOS :p
					if (petri.inDish(x, y) && petri.getOpponent(x, y) == panorama.ellos.id
							&& ((Math.abs(x - pos.x) == radio) || (Math.abs(y - pos.y)) == radio))
					{
						dif = Math.abs(petri.getEnergy(x, y) - ene);

						if (ene >= petri.getEnergy(x, y))
						{
							suEP = dif;
							miEP = petri.getEnergy(x, y) * 0.075f;
						} else
						{
							suEP = ene * 0.075f;
							miEP = dif;
						}

						if (suEP / miEP > fAtacar && petri.getEnergy(x, y) < eminima
						// && movimientoPosible(p, new Point(pos.x,pos.y), mov, exMov))
						)
						{
							// pfinal = new Point(p);
							// mov ya esta cargado en la funcion movimientoPosible
							// eminima = petri.getEnergy(x, y);

							Vector<Movement> pasos = new Vector<Movement>();

							panorama.mapa.nPasos = getCantidadMov(p, new Point(pos.x, pos.y));

							if (panorama.mapa.nPasos > 3)
								panorama.mapa.nPasos = 3; // TODO ESTO ES MODIFICABLE

							boolean posible = false;
							movimientoPosible(p, new Point(pos.x, pos.y), panorama.mapa.nPasos, exMov, pasos,
									posible);

							if (pasos.size() == panorama.mapa.nPasos)
							{
								// mov es el primer elemento de pasos
								mov.dx = pasos.get(0).dx;
								mov.dy = pasos.get(0).dy;
								eminima = petri.getEnergy(x, y);
							}
						}
					}
				}
			radio++;

			if (mov.isMoved())
			{
				// panorama.mapa.get(pfinal.x, pfinal.y).libre = panorama.codigoColonia == Cte.FINALIZAR;//
				return true;
			}
			eminima = 50000;
		}
		return false;
	}

	private boolean escapar(Movement mov)
	{
		float dif = 0; // diferencia de energia absoluta entre los dos
		float miEP = 0; // ENERGIAS PERDIDAS
		float suEP = 0;
		Point p;
		int radio = 1;

		while (radio <= radioEscape)
		{
			for (int x = pos.x - radio; x <= pos.x + radio; x++)
				for (int y = pos.y - radio; y <= pos.y + radio; y++)
				{
					p = new Point(x, y);
					if (petri.inDish(x, y) && petri.getOpponent(x, y) == panorama.ellos.id
							&& ((Math.abs(x - pos.x) == radio) || (Math.abs(y - pos.y)) == radio))
					{
						dif = Math.abs(petri.getEnergy(x, y) - ene);

						if (ene >= petri.getEnergy(x, y))
						{
							suEP = dif;
							miEP = petri.getEnergy(x, y) * 0.075f;
						} else
						{
							suEP = ene * 0.075f;
							miEP = dif;
						}

						if (suEP / miEP < fEscapar
								&& petri.inDish(pos.x - movimientoRelativo(p, new Point(pos.x, pos.y)).dx,
										pos.y - movimientoRelativo(p, new Point(pos.x, pos.y)).dy))
						// si la posicion nueva no se va de petri, escapa
						{
							// movimientoPosible(pos, p, mov, exMov);
							mov.dx = -movimientoRelativo(p, new Point(pos.x, pos.y)).dx;
							mov.dy = -movimientoRelativo(p, new Point(pos.x, pos.y)).dy;
							return true; // escapa ante el primero q
							// encuentra
						}
					}
				}
			radio++;
		}

		return mov.isMoved();
	}

	/**
	 * Utiliza elementos de panorama para encontrar mejores posiciones de
	 * nutrientes.
	 */
	private void alimentarse(Movement mov)
	{
		Point p;
		int radio = 1;
		int nivelmax = panorama.mapa.get(pos.x, pos.y).nivelDeComida;
		int nivelmin = panorama.mapa.numNiveles / 3;
		// if(petri.getDistNutri()==Cte.LATTICE)
		// nivelmin = panorama.mapa.numNiveles/2;

		boolean saltoActivado = petri.getDistNutri() == Cte.GAUSS || petri.getDistNutri() == Cte.SORPRESA;

		Point pfinal = new Point();
		if (saltoActivado && (panorama.mapa.get(pos.x, pos.y).nivelDeComida <= nivelmin))
			nivelmax = (panorama.mapa.numNiveles * 2) / 3;
		if (petri.getDistNutri() == Cte.LATTICE && viajero
				&& (panorama.mapa.get(pos.x, pos.y).nivelDeComida <= nivelmin))
			nivelmax = panorama.mapa.numNiveles / 2;

		// System.out.println("Buscando " + nivelmax + "/" + panorama.mapa.numNiveles + " en "
		// + panorama.mapa.get(pos.x, pos.y).nivelDeComida);

		while (radio <= radioComida)
		{
			for (int x = pos.x - radio; x <= pos.x + radio; x++)
				for (int y = pos.y - radio; y <= pos.y + radio; y++)
				{
					p = new Point(x, y);

					if (petri.inDish(x, y)
							&& ((Math.abs(x - pos.x) == radio) || (Math.abs(y - pos.y)) == radio)
							&& panorama.mapa.get(x, y).libre
							&& (petri.getEnergy(x, y) == 0 || panorama.ellos.habitantes > 80)
							&& panorama.mapa.get(x, y).nivelDeComida > nivelmax
							// && movimientoPosible(p, new Point(pos.x,pos.y), mov, exMov)
							&& (panorama.turnoColonia < Cte.TURNOARRANQUE || !saltoActivado || (saltoActivado && panorama.mapa
									.get(x, y).densidadEllos.energia < panorama.mapa.get(pos.x, pos.y).densidadNos.energia)))
					{
						Vector<Movement> pasos = new Vector<Movement>();

						panorama.mapa.nPasos = getCantidadMov(p, new Point(pos.x, pos.y));

						if (panorama.mapa.nPasos > 3)
							panorama.mapa.nPasos = 3; // TODO ESTO ES MODIFICABLE

						boolean posible = false;

						movimientoPosible(p, new Point(pos.x, pos.y), panorama.mapa.nPasos, exMov, pasos,
								posible);

						if (pasos.size() == panorama.mapa.nPasos)
						{
							nivelmax = panorama.mapa.get(x, y).nivelDeComida;

							mov.dx = pasos.get(0).dx;
							mov.dy = pasos.get(0).dy;

							pfinal = new Point(p);
						}
					}
				}

			// PARA QUE SE MUEVAN MENOS
			if (radio < 3)
				if (petri.getNutrient(pfinal.x, pfinal.y) < 1.1 * petri.getNutrient(pos.x, pos.y))
				{
					mov.dx = 0;
					mov.dy = 0;
				}

			if (mov.isMoved())
			{
				panorama.mapa.get(pfinal.x, pfinal.y).libre = false;
				return;
			}

			radio++;
		}

		// TODO Podría agregarse la distribución sorpresa acá y un poquito más arriba
		// TODO AGREGO LATTICE
		if ((saltoActivado || petri.getDistNutri() == Cte.LATTICE) && !mov.isMoved())
		{
			nivelmax = panorama.mapa.get(pos.x, pos.y).nivelDeComida;
			while (radio <= radioComida)
			{
				for (int x = pos.x - radio; x <= pos.x + radio; x++)
					for (int y = pos.y - radio; y <= pos.y + radio; y++)
					{
						p = new Point(x, y);
						if (petri.inDish(x, y)
								&& ((Math.abs(x - pos.x) == radio) || (Math.abs(y - pos.y)) == radio)
								&& panorama.mapa.get(x, y).libre
								&& (petri.getEnergy(x, y) == 0 || panorama.ellos.habitantes > 80)
								&& panorama.mapa.get(x, y).nivelDeComida > nivelmax
								&& (panorama.turnoColonia < Cte.TURNOARRANQUE || (panorama.mapa.get(x, y).densidadEllos.energia < panorama.mapa
										.get(pos.x, pos.y).densidadNos.energia)))
						{
							Vector<Movement> pasos = new Vector<Movement>();

							panorama.mapa.nPasos = getCantidadMov(p, new Point(pos.x, pos.y));

							if (panorama.mapa.nPasos > 3)
								panorama.mapa.nPasos = 3; // ESTO ES MODIFICABLE

							boolean posible = false;

							movimientoPosible(p, new Point(pos.x, pos.y), panorama.mapa.nPasos, exMov, pasos,
									posible);

							if (pasos.size() == panorama.mapa.nPasos)
							{
								nivelmax = panorama.mapa.get(x, y).nivelDeComida;

								mov.dx = pasos.get(0).dx;
								mov.dy = pasos.get(0).dy;

								pfinal = new Point(p);
							}
						}
					}

				// PARA QUE SE MUEVAN MENOS
				// if (radio < 3)
				// if (petri.getNutrient(pfinal.x, pfinal.y) < 1.1 * petri.getNutrient(pos.x, pos.y))
				// {
				// mov.dx = 0;
				// mov.dy = 0;
				// }

				if (mov.isMoved())
				{
					panorama.mapa.get(pfinal.x, pfinal.y).libre = false;
					return;
				}

				radio++;
			}
		}
	}

	/*
	 * FUNCIONES AUXILIARES
	 */
	/**
	 * Verifica si es posible llegar a p con el movimiento mov desde el punto de partida s, sin usar el
	 * movimiento exmov. Sino, Busca un
	 * movimiento relativo mov para llegar desde pos hasta p. Si lo encuentra,
	 * devuelve true. mov solo se modifica en caso de devolver true
	 */
	// VA COMO PARAMETRO SEGUNDO EL PUNTO DE PARTIDA y como cuarto el movimiento anterior
	/*
	 * private boolean movimientoPosible(Point p, Point s, Movement mov, Movement exmov)
	 * {
	 * mov.dx = movimientoRelativo(p, s).dx;
	 * mov.dy = movimientoRelativo(p, s).dy;
	 * if (petri.inDish(s.x + mov.dx, s.y + mov.dy))
	 * if (petri.getOpponent(s.x + mov.dx, s.y + mov.dy) != panorama.nos.id)
	 * return true;
	 * // sino, busca en los adyacentes un camino posible (x ahora solo los
	 * // adyacentes +/-1)
	 */
	// encuentra el vecino original
	/*
	 * 0 1 2 7 3 6 5 4
	 */
	/*
	 * Movement vecinos[] = { new Movement(-1, 1), new Movement(0, 1), new Movement(1, 1),
	 * new Movement(1, 0), new Movement(1, -1), new Movement(0, -1), new Movement(-1, -1),
	 * new Movement(-1, 0) };
	 * for (int i = 0; i < vecinos.length; i++)
	 * {
	 * if (vecinos[i].dx == mov.dx && vecinos[i].dy == mov.dy)
	 * {
	 * if (i - 1 >= 0)
	 * if (petri.inDish(s.x + vecinos[i - 1].dx, s.y + vecinos[i - 1].dy))
	 * if (petri.getOpponent(s.x + vecinos[i - 1].dx, s.y + vecinos[i - 1].dy) != panorama.nos.id
	 * && (vecinos[i - 1].dx != -exmov.dx || vecinos[i - 1].dy != -exmov.dy))
	 * {
	 * mov.dx = vecinos[i - 1].dx;
	 * mov.dy = vecinos[i - 1].dy;
	 * return true;
	 * }
	 * if (i == 0)
	 * if (petri.inDish(s.x + vecinos[7].dx, s.y + vecinos[7].dy))
	 * if (petri.getOpponent(s.x + vecinos[7].dx, s.y + vecinos[7].dy) != panorama.nos.id
	 * && (vecinos[7].dx != -exmov.dx || vecinos[7].dy != -exmov.dy))
	 * {
	 * mov.dx = vecinos[7].dx;
	 * mov.dy = vecinos[7].dy;
	 * return true;
	 * }
	 * if (i + 1 <= 7)
	 * if (petri.inDish(s.x + vecinos[i + 1].dx, s.y + vecinos[i + 1].dy))
	 * if (petri.getOpponent(s.x + vecinos[i + 1].dx, s.y + vecinos[i + 1].dy) != panorama.nos.id
	 * && (vecinos[i + 1].dx != -exmov.dx || vecinos[i + 1].dy != -exmov.dy))
	 * {
	 * mov.dx = vecinos[i + 1].dx;
	 * mov.dy = vecinos[i + 1].dy;
	 * return true;
	 * }
	 * if (i == 7)
	 * if (petri.inDish(s.x + vecinos[0].dx, s.y + vecinos[0].dy))
	 * if (petri.getOpponent(s.x + vecinos[0].dx, s.y + vecinos[0].dy) != panorama.nos.id
	 * && (vecinos[0].dx != -exmov.dx || vecinos[0].dy != -exmov.dy))
	 * {
	 * mov.dx = vecinos[0].dx;
	 * mov.dy = vecinos[0].dy;
	 * return true;
	 * }
	 * }
	 * }
	 * mov = new Movement(0, 0);
	 * return false;
	 * }
	 */

	/**
	 * Verifica si es posible llegar a p desde el punto de partida s, utilizando solo t turnos. LO debe hacer
	 * sin usar el movimiento exmov. Evalua una dispersión de +/-1 (ej si va al norte, evalua NO y NE).
	 * IMPORTANTE: solo busca un camino posible y devuelve el primer movimiento hacia dicho camino (PRIMER
	 * ELEMENTO
	 * DE pasos). NO BUSCA
	 * EL OPTIMO.
	 * inicialesProbados indica el numero de pasos de radio 1 q hizo desde S. Por lo visto, serian 3 posibles
	 * (como el ejemplo
	 * de N,NO.NE. Hasta ahora solo se me ocurrio eso para condicion de finalizacion.
	 * PD: ES RECURSIVA JOJOJO
	 */
	/*
	 * SE DEBE LLAMAR ASI
	 * pasos=new Vector<Movement>();
	 * t=3;
	 * p=new Point(x,y);
	 * s=new Point(pos);
	 * pasosprobados=0;
	 * movimientoPOs...
	 */
	private void movimientoPosible(Point p, Point s, int t, Movement exmov, Vector<Movement> pasos,
			boolean posible)
	{
		// condicion de finalizacion de exito
		if (pasos.size() == t)
		{
			posible = true;
			return;
		}

		// desde el s actual intenta el movimiento directo:
		Movement mov = new Movement();
		mov.dx = movimientoRelativo(p, s).dx;
		mov.dy = movimientoRelativo(p, s).dy;

		// si el movimiento es valido, avanza, guardando posible=true;
		if (petri.inDish(s.x + mov.dx, s.y + mov.dy))
			if (petri.getOpponent(s.x + mov.dx, s.y + mov.dy) != panorama.nos.id
					&& !enemigoFuerte(new Point(s.x + mov.dx, s.y + mov.dy), s))
				if (pasos.size() != 0 || (pasos.size() == 0 && (mov.dx != -exmov.dx || mov.dy != -exmov.dy)))
				{
					posible = true;
					pasos.add(mov);

					movimientoPosible(p, new Point(s.x + mov.dx, s.y + mov.dy), t, exmov, pasos, posible);

					// condicion de finalizacion de exito
					if (pasos.size() == t)
					{
						posible = true;
						return;
					}
				}

		// ACA DEBE VERIFICAR LAS ALTERNATIVAS A MOVERSE EN LA DIRECCION DIRECTA
		// POR LO PRONTO ===> SOLUCION FEA

		Movement v[] = { new Movement(-1, 1), new Movement(0, 1), new Movement(1, 1), new Movement(1, 0),
				new Movement(1, -1), new Movement(0, -1), new Movement(-1, -1), new Movement(-1, 0) };

		for (int i = 0; i < v.length; i++)
		{
			if (v[i].dx == mov.dx && v[i].dy == mov.dy)
			{

				if (i - 1 >= 0)
					if (petri.inDish(s.x + v[i - 1].dx, s.y + v[i - 1].dy))
						if (petri.getOpponent(s.x + v[i - 1].dx, s.y + v[i - 1].dy) != panorama.nos.id
								&& !enemigoFuerte(new Point(s.x + v[i - 1].dx, s.y + v[i - 1].dy), s))
							if (pasos.size() != 0
									|| (pasos.size() == 0 && (mov.dx != -exmov.dx || mov.dy != -exmov.dy)))
							{
								mov.dx = v[i - 1].dx;
								mov.dy = v[i - 1].dy;

								pasos.add(mov);
								posible = true;

								movimientoPosible(p, new Point(s.x + mov.dx, s.y + mov.dy), t, exmov, pasos,
										posible);

								// condicion de finalizacion de exito
								if (pasos.size() == t)
								{
									posible = true;
									return;
								}
							}

				if (i == 0)
					if (petri.inDish(s.x + v[7].dx, s.y + v[7].dy))
						if (petri.inDish(s.x + v[7].dx, s.y + v[7].dy))
							if (petri.getOpponent(s.x + v[7].dx, s.y + v[7].dy) != panorama.nos.id
									&& !enemigoFuerte(new Point(s.x + v[7].dx, s.y + v[7].dy), s))
								if (pasos.size() != 0
										|| (pasos.size() == 0 && (mov.dx != -exmov.dx || mov.dy != -exmov.dy)))
								{
									mov.dx = v[7].dx;
									mov.dy = v[7].dy;

									posible = true;
									pasos.add(mov);

									movimientoPosible(p, new Point(s.x + mov.dx, s.y + mov.dy), t, exmov,
											pasos, posible);

									// condicion de finalizacion de exito
									if (pasos.size() == t)
									{
										posible = true;
										return;
									}
								}

				if (i + 1 <= 7)
					if (petri.inDish(s.x + v[i + 1].dx, s.y + v[i + 1].dy))
						if (petri.inDish(s.x + v[i + 1].dx, s.y + v[i + 1].dy))
							if (petri.getOpponent(s.x + v[i + 1].dx, s.y + v[i + 1].dy) != panorama.nos.id
									&& !enemigoFuerte(new Point(s.x + v[i + 1].dx, s.y + v[i + 1].dy), s))
								if (pasos.size() != 0
										|| (pasos.size() == 0 && (mov.dx != -exmov.dx || mov.dy != -exmov.dy)))
								{
									mov.dx = v[i + 1].dx;
									mov.dy = v[i + 1].dy;

									posible = true;
									pasos.add(mov);

									movimientoPosible(p, new Point(s.x + mov.dx, s.y + mov.dy), t, exmov,
											pasos, posible);

									// condicion de finalizacion de exito
									if (pasos.size() == t)
									{
										posible = true;
										return;
									}
								}

				if (i == 7)
					if (petri.inDish(s.x + v[0].dx, s.y + v[0].dy))
						if (petri.inDish(s.x + v[0].dx, s.y + v[0].dy))
							if (petri.getOpponent(s.x + v[0].dx, s.y + v[0].dy) != panorama.nos.id
									&& !enemigoFuerte(new Point(s.x + v[0].dx, s.y + v[0].dy), s))
								if (pasos.size() != 0
										|| (pasos.size() == 0 && (mov.dx != -exmov.dx || mov.dy != -exmov.dy)))
								{
									mov.dx = v[0].dx;
									mov.dy = v[0].dy;
									posible = true;
									pasos.add(mov);

									movimientoPosible(p, new Point(s.x + mov.dx, s.y + mov.dy), t, exmov,
											pasos, posible);

									// condicion de finalizacion de exito
									if (pasos.size() == t)
									{
										posible = true;
										return;
									}
								}
			}
		}

		// UNA VEZ CHEQUEADAS TODAS LAS POSIBILIDADES, SI NO AVANZA, NUNCA LLEGA A t Y X TANTO DEBE
		// VOLVER PARA ATRAS,
		// X LO Q ANTES BORRA EL INTENTO FALLIDO
		if (pasos.size() != 0 && pasos.size() != t)
		{
			pasos.removeElementAt(pasos.size() - 1);
		}

		// condicion de finalizacion de fracaso
		// if(pasos.size()!=t)
		// posible=false;
		// else
		// posible=true;
	}

	/**
	 * Funcion auxiliar, devuelve verdadero si hay un enemigo en p que sea mas fuerte q la referencia en s,
	 * considerando un fAtacar de 0.4
	 */
	private boolean enemigoFuerte(Point p, Point s)
	{

		if (petri.getOpponent(p.x, p.y) != panorama.ellos.id)
			return false; // no hay enemigo
		float miE = petri.getEnergy(s.x, s.y);
		float suE = petri.getEnergy(p.x, p.y);
		float dif = Math.abs(suE - miE);
		float suEP, miEP;
		if (miE >= suE)
		{
			suEP = dif;
			miEP = suE * 0.075f;
		} else
		{
			miEP = dif;
			suEP = miE * 0.075f;
		}

		if (suEP / miEP > 0.4)
			return false; // enemigo debil (o maso)
		else
			return true;
	}

	private int getCantidadMov(Point p, Point s)
	{
		return Math.max(Math.abs(p.x - s.x), Math.abs(p.y - s.y));
	}

	Movement movimientoRelativo(Point p, Point s)
	{
		Movement mov = new Movement();

		if (s.x > p.x)
			mov.dx = -1;
		if (s.y > p.y)
			mov.dy = -1;
		if (s.x == p.x)
			mov.dx = 0;
		if (s.y == p.y)
			mov.dy = 0;
		if (s.x < p.x)
			mov.dx = 1;
		if (s.y < p.y)
			mov.dy = 1;

		return mov;
	}

	// ---------------------------------------------------------------------------
	// ---------------------------------------------------------------------------
	// - AQUI COMIENZA LA CLASE PANORAMA Y SUS DERIVADOS. DEJAR SIEMPRE ABAJO ----
	// ---------------------------------------------------------------------------
	// ---------------------------------------------------------------------------

	/**
	 * Contiene todas las variables del panorama o la situación actual del
	 * petri.
	 */
	public class Panorama
	{
		/** Mapa del petri y sus características por celda */
		Mapa mapa;

		/** Turno actual de la colonia */
		int turnoColonia = 0;

		/** Estado que decide las acciones a realizar por toda la colonia */
		int codigoColonia = Cte.NORMAL;

		Colonia nos;
		Colonia ellos;

		Map<Integer, Vector<Point>> atacantes;
		int contadorAtacantes;

		// Variables agregadas caso lattice, se modifican dentro de la misma funcion q las calcula
		Point centroMax;
		int radioMax;

		Panorama()
		{
			mapa = new Mapa();
			turnoColonia = 0;
			codigoColonia = Cte.NORMAL;
			nos = new Colonia();
			ellos = new Colonia();
			atacantes = new HashMap<Integer, Vector<Point>>();
		}

		/** Determina el nivel de comida de cada celda del mapa y libera todos los destinos (true) */
		void actualizar()
		{
			// Anulo valores energéticos que voy a calcular
			nos.energiaProm = 0f;
			nos.energiaTotal = 0f;
			nos.habitantes = 0;
			nos.tendencia = 0;

			ellos.energiaProm = 0f;
			ellos.energiaTotal = 0f;
			ellos.habitantes = 0;
			ellos.tendencia = 0;

			mapa.comidaMax = 0;

			mapa.densidadEllosMax = 0;
			mapa.densidadNosMax = 0;

			// Primer barrido para detectar máximo de comida, liberar el mapa y calcular energías
			for (int x = 0; x < mapa.size(); x++)
				for (int y = 0; y < mapa.size(); y++)
				{
					if (petri.inDish(x, y))
					{
						// Busca el máximo de comida
						if (mapa.comidaMax < petri.getNutrient(x, y))
							mapa.comidaMax = petri.getNutrient(x, y);

						// Resetea el mapa, dejando los destinos libres
						mapa.get(x, y).libre = true;

						// Cuenta los habitantes de ambas colonias y acumula energia total por colonia
						if (petri.getOpponent(x, y) == nos.id)
						{
							nos.habitantes++;
							nos.energiaTotal += petri.getEnergy(x, y);
						}
						if (petri.getOpponent(x, y) == ellos.id)
						{
							ellos.habitantes++;
							ellos.energiaTotal += petri.getEnergy(x, y);
						}

						// Llena la celda (x,y) con las densidades de energia y poblacion
						calcularDensidad(x, y);
						mapa.densidadNosMax = (mapa.get(x, y).densidadNos.energia > mapa.densidadNosMax) ? mapa
								.get(x, y).densidadNos.energia
								: mapa.densidadNosMax;

						mapa.densidadEllosMax = (mapa.get(x, y).densidadEllos.energia > mapa.densidadEllosMax) ? mapa
								.get(x, y).densidadEllos.energia
								: mapa.densidadEllosMax;
					}
				}

			// Cálculo de energía promedio, máxima y tendencias
			nos.energiaProm = nos.energiaTotal / (float) nos.habitantes;
			ellos.energiaProm = ellos.energiaTotal / (float) ellos.habitantes;
			if (nos.energiaTotal > nos.energiaMax)
				nos.energiaMax = nos.energiaTotal;
			if (ellos.energiaTotal > ellos.energiaMax)
				ellos.energiaMax = ellos.energiaTotal;
			nos.energia.remove(0);
			ellos.energia.remove(0);
			nos.energia.add(nos.energiaTotal);
			ellos.energia.add(ellos.energiaTotal);

			for (int i = 1; i < nos.energia.size(); i++)
			{
				if (nos.energia.get(i - 1) != 0 && ellos.energia.get(i - 1) != 0)
				{
					nos.tendencia += 100f * (nos.energia.get(i) - nos.energia.get(i - 1))
							/ nos.energia.get(i - 1);
					ellos.tendencia += 100f * (ellos.energia.get(i) - ellos.energia.get(i - 1))
							/ ellos.energia.get(i - 1);
				}
			}

			// Calculo de niveles por varianza
			// (Esto se podria optimizar)
			float varianza = calcularVarianzaNutri();
			int numNivel = calcularNumNivel(varianza);

//			System.out.println("Distribución" + petri.getDistNutri());
//			// System.out.println("Varianza" + varianza);
//			System.out.println("Número de niveles" + numNivel);
//			// System.out.println("Comida" + petri.getNutrient(pos.x, pos.y));
//			System.out.println();
			mapa.setNumNiveles(numNivel);

			switch (petri.getDistNutri())
			{
			case Cte.BAR:
				// mapa.setNumNiveles(1);
				break;
			case Cte.GAUSS:
				// mapa.setNumNiveles(8);
				break;
			case Cte.RINGS:
				// mapa.setNumNiveles(2);
				break;
			case Cte.LATTICE:
				calcularRadioMax();
				// mapa.setNumNiveles(4);
				break;
			default:
				// mapa.setNumNiveles(4);
				break;
			}

			// Segundo barrido determina los niveles de comida
			for (int x = 0; x < mapa.size(); x++)
				for (int y = 0; y < mapa.size(); y++)
					if (petri.inDish(x, y))
						mapa.get(x, y).nivelDeComida = mapa.calcularNivel(petri.getNutrient(x, y));

//			int codAnt = new Integer(codigoColonia);

			actualizarEstado();

//			if (codAnt != codigoColonia)
//			{
//				switch (codigoColonia)
//				{
//				case Cte.AGUANTAR:
//					System.out.println("Aguantando");
//					break;
//				case Cte.NORMAL:
//					System.out.println("Normal");
//					break;
//				case Cte.FINALIZAR:
//					System.out.println("Finalizando");
//					break;
//				default:
//					break;
//				}
//				System.out.flush();
//			}

			// TODO PRUEBA DE MAPA DE BICHOS DEDICADOS A ATACAR
			if (codigoColonia == Cte.FINALIZAR)
				actualizarAtacantes();
		}

		/**
		 * Calcula la zona que maximiza la disponibilidad de comida
		 */
		private void calcularRadioMax()
		{
			radioMax = 10;
			centroMax = new Point(0, 0);
			float nutPromMax = 0;

			float nutProm = 0; // sin contar fuera de petri y sin espacios vacios TODO
			int contador = 0;

			for (int x = radioMax; x < Defs.DIAMETER - radioMax; x++)
				for (int y = radioMax; y < Defs.DIAMETER - radioMax; y++)
				{
					nutProm = 0;
					contador = 0;
					for (int i = x - radioMax; i < x + radioMax; i++)
						for (int j = x - radioMax; j < x + radioMax; j++)
							if (petri.inDish(i, j) && petri.getNutrient(x, y) > 50)
							{
								nutProm += petri.getNutrient(i, j);
								contador++;
							}
					nutProm /= contador;
					if (nutProm > nutPromMax)
					{
						nutPromMax = nutProm;
						centroMax = new Point(x, y);
					}
				}
		}

		private int calcularNumNivel(float varianza)
		{
			return (int) (Math.pow(varianza, 0.5) / 90) + 2;
		}

		private float calcularVarianzaNutri()
		{
			// considerar solo los valores !=0

			float noceros = 0;

			float media = 0, varianza = 0;

			for (int x = 0; x < Defs.DIAMETER; x++)
				for (int y = 0; y < Defs.DIAMETER; y++)
				{
					media += petri.getNutrient(x, y);
					if (petri.getNutrient(x, y) > 50)
						noceros++;
				}
			media /= noceros;

			for (int x = 0; x < Defs.DIAMETER; x++)
				for (int y = 0; y < Defs.DIAMETER; y++)
				{
					if (petri.getNutrient(x, y) > 50)
						varianza += Math.pow((petri.getNutrient(x, y) - media), 2);
				}

			varianza /= noceros;

			return varianza;
		}

		/**
		 * Esta funcion determina n bichos que van a atacar en la ronda, basandose en la cercania a los
		 * enemigos.
		 * Solo considera los bichos que pueden moverse a esa cercania
		 */
		private void actualizarAtacantes() // TODO MODIFICADO
		{
			// TODO ESTO SE PUEDE INCLUIR DENTRO DE LOS CICLOS DE BUSQUEDA DEL
			// ACTUALIZAR. LO DEJO ASI POR
			// COMODIDDAD
			// POR AHORA. EL PRINCIPAL PROBLEMA SON LOS FACTORES, YA Q ACA HACE
			// UNA BUSQUEDA GLOBAL. FIJO UN
			// RADIO
			// MAXIMO IGUAL A DEFS.DIAMETER PARA INCLUIR TODo EL PETRI, Y HAGO
			// fAtacar=0.4 que me parece
			// prudente para
			// guardarlo como posible atacante. luego el bicho decide si va a o
			// no. (esto mete ruido q se
			// tendra q arreglar
			// luego ------>!!!!

			int rmax = Defs.DIAMETER;
			int radio = 1;
			float dif, suEP, miEP;
			Point p = new Point(0, 0);
			Movement mov = new Movement(0, 0);
			Movement exmov = new Movement(0, 0);

			atacantes.clear(); // estos son los posibles atacantes, se guarda el
			// valor de radio y su posicion.

			boolean continua = true;

			for (int posx = 0; posx < mapa.size(); posx++)
				for (int posy = 0; posy < mapa.size(); posy++)
				{

					continua = true;
					exmov = new Movement(0, 0);
					mov = new Movement(0, 0);
					radio = 1;

					if (petri.getOpponent(posx, posy) == nos.id)
						while (radio <= rmax && continua)
						{
							for (int xi = posx - radio; xi <= posx + radio; xi++)
								for (int yi = posy - radio; yi <= posy + radio; yi++)
								{
									p = new Point(xi, yi);
									// Busca posibles enemigos y guarda el radio
									// en el q se encuentra
									if (petri.inDish(xi, yi)
											&& petri.getOpponent(xi, yi) == ellos.id
											&& ((Math.abs(xi - posx) == radio) || (Math.abs(yi - posy)) == radio))
									{
										dif = Math.abs(petri.getEnergy(xi, yi) - petri.getEnergy(posx, posy));

										if (petri.getEnergy(posx, posy) >= petri.getEnergy(xi, yi))
										{
											suEP = dif;
											miEP = petri.getEnergy(xi, yi) * 0.075f;
										} else
										{
											suEP = petri.getEnergy(posx, posy) * 0.075f;
											miEP = dif;
										}
										// TODO aca pongo un fAtacar=0.4, valido
										// para finalizar. POR AHORA
										// PUEDE ANDAR

										if (suEP / miEP > 0.4)
										{
											Vector<Movement> pasos = new Vector<Movement>();

											panorama.mapa.nPasos = getCantidadMov(p, new Point(posx, posy));

											if (panorama.mapa.nPasos > 3)
												panorama.mapa.nPasos = 3; // TODO ESTO ES MODIFICABLE

											boolean posible = false;

											movimientoPosible(p, new Point(posx, posy), panorama.mapa.nPasos,
													exmov, pasos, posible);

											if (pasos.size() == panorama.mapa.nPasos)
											{
												mov.dx = pasos.get(0).dx;
												mov.dy = pasos.get(0).dy;
											}
										}
									}
								}

							if (mov.isMoved())
							{
								// En este punto, pfinal tiene la ubicacion de
								// un MO que puede atacar a un
								// enemigo y radio
								// tiene su distancia. Se verifica si esta
								// entrada existe, sino se agrega.
								// multimaps no esta soportado, asiq guardo un
								// array de puntos si se repite el
								// radio
								if (!atacantes.containsKey(radio))
								{
									Vector<Point> pactual = new Vector<Point>();
									pactual.add(new Point(posx, posy));
									atacantes.put(radio, pactual);
								} else
								// hay que actualizar el array de puntos con
								// este radio
								{
									Vector<Point> aux = new Vector<Point>();
									aux = atacantes.get(radio);
									aux.add(new Point(posx, posy));
									atacantes.remove(radio);
									atacantes.put(radio, aux);
								}

								continua = false;
							}
							radio++;
						}
				}

			// Contador de atacantes
			contadorAtacantes = 0;

			// Vector<Point> a=new Vector<Point>();

			for (int r = 1; r < Defs.DIAMETER; r++)
			{
				if (atacantes.get(r) != null)
					contadorAtacantes += atacantes.get(r).size();
			}
		}

		/** Define el codigo de colonia */
		private void actualizarEstado()
		{
			if (petri.getDistNutri() == Cte.BAR && codigoColonia == Cte.FINALIZAR)
				return;
			codigoColonia = Cte.NORMAL;

			if (panorama.turnoColonia > Cte.TURNOARRANQUE)
			{
				// AGUANTAR si tenemos ventaja en energia y promedio y ellos son pocos
				if (nos.habitantes > ellos.habitantes && nos.energiaTotal > 1.2 * ellos.energiaTotal
						&& /* cond principal */ellos.habitantes < 15 && nos.tendencia > 0
						&& nos.energiaTotal > 50000)
					codigoColonia = Cte.AGUANTAR;

				// FINALIZAR solo cuando la tendencia cae, pero
				// tenemos ventaja
				int energMax;
				switch (petri.getDistNutri())
				{
				case Cte.BAR:
					energMax = 50000;
					break;
				default:
					energMax = 80000;
					break;
				}

				if (nos.energiaTotal < 0.85 * nos.energiaMax && ellos.energiaTotal < nos.energiaTotal
						&& nos.energia.lastElement() > energMax)
					codigoColonia = Cte.FINALIZAR;

				if (petri.getDistNutri() == Cte.LATTICE && ellos.energiaTotal - nos.energiaTotal > 70000)
					codigoColonia = Cte.SUICIDARSE;
			}
		}

		/** Calcula la densidad de energía y población de ambas colonias para la celda (x,y) */
		public void calcularDensidad(int x, int y)
		{
			float bichosNos = 0, bichosEllos = 0, espacios = 0;
			float energNos = 0, energEllos = 0;
			int r = radioDensidad;

			for (int i = -r; i <= r; i++)
				for (int j = -r; j <= r; j++)
					if (petri.inDish(x + i, y + j))
					{
						espacios++;
						if (petri.getOpponent(x + i, y + j) == nos.id)
						{
							bichosNos++;
							energNos += petri.getEnergy(x + i, y + j);
						}
						if (petri.getOpponent(x + i, y + j) == ellos.id)
						{
							bichosEllos++;
							energEllos += petri.getEnergy(x + i, y + j);
						}
					}
			mapa.get(x, y).densidadNos = new Densidad(energNos / espacios, bichosNos / espacios);
			mapa.get(x, y).densidadEllos = new Densidad(energEllos / espacios, bichosEllos / espacios);
		}

		/** Características generales de la colonia */
		public class Colonia
		{
			/** Identificacion de colonia. Debe inicializarse fuera. Valor por defecto -1 */
			int id;
			/** @return Máxima Energía Total historica registrada */
			float energiaMax;
			/** @return EnergiaTotal/Numero de Habitantes */
			float energiaProm;
			/** @return Resultado de la suma las energias de todos los habitantes */
			float energiaTotal;
			/** @return Tendencia energética en base a las ultimas 10 energias totales */
			float tendencia;
			/** @return Número de habitantes en la colonia */
			int habitantes;
			private Vector<Float> energia;

			/** Crea una colonia nula con <code>id -1</code>, es decir el vacío */
			Colonia()
			{
				id = -1;
				energiaMax = 0f;
				energiaProm = 0f;
				energiaTotal = 0f;
				tendencia = 0f;
				habitantes = 0;
				energia = new Vector<Float>();
				energia.setSize(10);
				for (int i = 0; i < energia.size(); i++)
					energia.set(i, 0f);
			}
		}

		class Mapa
		{
			public int nPasos;
			private Celda[][] matriz;
			/** Nutriente máximo obtenido mediante <code>petri.getNutrient()</code> */
			float comidaMax;
			/** Delimitadores porcentuales de niveles de Comida */
			private Vector<Float> niveles;
			/** @return Nivel o escalón máximo posible en un entero no negativo. */
			private int numNiveles;
			/**
			 * atacantes guarda (en orden) los posibles atacantes nuestros del mapa, segun la distancia al
			 * enemigo
			 * de ataque posible mas cercano. los valores key son los radios propiamente dichos
			 */

			/** densidad de energia nuestra maxima en el petri */
			float densidadNosMax;
			/** densidad de energia de ellos maxima en el petri */
			float densidadEllosMax;

			Mapa()
			{
				setNumNiveles(10);
				matriz = new Celda[Defs.DIAMETER][Defs.DIAMETER];
				for (int x = 0; x < matriz.length; x++)
					for (int y = 0; y < matriz[0].length; y++)
						matriz[x][y] = new Celda();
				comidaMax = 0f;
				densidadNosMax = 0;
				densidadEllosMax = 0;
			}

			/** @return La celda con sus atributos en la posicion (x,y) */
			Celda get(int x, int y)
			{
				return matriz[x][y];
			}

			/** @return Tamaño del mapa, coincide con Defs.DIAMETER pero... */
			public int size()
			{
				return matriz.length;
			}

			/**
			 * Devuelve 0 si es menor al primer escalon, y nivelMax si es el
			 * maximo
			 */
			public int calcularNivel(float nut)
			{
				for (int n = 0; n < niveles.size(); n++)
				{
					if (nut < niveles.get(n) * comidaMax)
					{
						return n;
					}
				}
				return numNiveles;
			}

			/** Fija la cantidad de niveles además del 0 o basal. Con escala lineal */
			public void setNumNiveles(int n)
			{
				numNiveles = n;
				niveles = new Vector<Float>();
				for (float i = 1; i <= numNiveles; i++)
				{
					niveles.add((i / (float) numNiveles) - (3f / (5f * numNiveles)));
				}
			}
		}

		/** Contiene las variables de la celda del mapa */
		public class Celda
		{
			/** @return Valor entero entre 0 y <code>nivelMax</code> */
			public int nivelDeComida;
			/** Densidad puntual de energía y población */
			Densidad densidadNos;
			/** Densidad puntual de energía y población */
			Densidad densidadEllos;
			/** @return <code>true</code> si nadie se dirige alli, sino <code>false</code> */
			boolean libre;

			Celda()
			{
				nivelDeComida = 0;
				densidadNos = new Densidad();
				densidadEllos = new Densidad();
				libre = false;
			}
		}

		/** Densidad puntual de energía y población */
		public class Densidad
		{
			/** @return Valor flotante entre 0 y 1 */
			float energia;
			/** @return Valor flotante entre 0 y 1 */
			float poblacion;

			Densidad(float ener, float pob)
			{
				energia = new Float(ener);
				poblacion = new Float(pob);
			}

			Densidad()
			{
				energia = 0;
				poblacion = 0;
			}
		}
	}

	/**
	 * Contiene los códigos de colonia, las distribuciones de nutrientes,
	 * factores metabólicos y radios por defecto
	 */
	final static public class Cte
	{
		// Códigos de colonia
		/**
		 * Modo normal. Toda la lógica general, sacando casos de finalizacion de escenario, deben
		 * tomarse aca.
		 */
		final static int NORMAL = 0;
		/** Activo para esquivar los pocos sobrevivientes enemigos para acumular mas puntos */
		final static int AGUANTAR = 1;
		/**
		 * Se activa cuando es necesario terminar la batalla, porque tenemos ventaja y estamos
		 * perdiendo puntos
		 */
		final static int FINALIZAR = 2;
		/** DEPRECATED (siempre quise escribir eso) */
		final static int SUICIDARSE = 3;
		// Distribuciones de nutrientes
		final static int BAR = 2;
		final static int RINGS = 3;
		final static int LATTICE = 4;
		final static int GAUSS = 5;
		final static int SORPRESA = 7;

		// Factores metabólicos por defecto
		final static float fNUTRIENTE = 0.01f;
		final static float fESCAPAR = 0;
		final static float fATACAR = 99999f;
		final static int TURNOARRANQUE = 100;

		// Radios de rastreo para diferentes variables
		final static int RADIODENSIDAD = 3;
		final static int RADIOATAQUE = 10;
		final static int RADIOCOMIDA = 5;
		final static int RADIOESCAPE = 2;
	}
}